#ifdef __cplusplus
extern "C" {
#endif

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/inotify.h>
#include <sys/stat.h>

#include <jni.h>
#include <android/log.h>

//#define LOG_ENABLE
#define VIEW_ACTION "android.intent.action.VIEW"
#define JNI_FUNC(f) Java_com_hzy_uninstall_UninstallCallback_##f

#ifdef LOG_ENABLE
#define LOG_TAG "unistall_jni"
#define LOGI(...) do { __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__); } while(0)
#define LOGD(...) do { __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__); } while(0)
#define LOGE(...) do { __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__); } while(0)
#else
#define LOGI(...) do { } while(0)
#define LOGD(...) do { } while(0)
#define LOGE(...) do { } while(0)
#endif

static void openWebpage(const char* url, const char* serial) {
	LOGD("Open url:[%s]", url);
	if (serial == NULL) {
		// 执行命令am start -a android.intent.action.VIEW -d $(url)调用浏览器打开url
		execlp("am", "am", "start", "-a", VIEW_ACTION, "-d", url, "-t", "text/html", (char *) NULL);
	} else {
		// 执行命令am start --user userSerial -a android.intent.action.VIEW -d $(url)调用浏览器打开url
		execlp("am", "am", "start", "--user", serial, "-a", VIEW_ACTION, "-d", url, "-t", "text/html", (char *) NULL);
	}
}

/**
 * appDir
 * appFileDir
 * appObservedFile
 * appLockFile
 * localHtml
 * remoteUrl
 * localUrl
 * userSerial
 */
static int initCallback(const char* appDir, const char* appFileDir, const char* appObservedFile, const char* appLockFile,
		const char* localHtml, const char* remoteUrl, const char* localUrl, const char* userSerial) {

	const char *url = NULL;

	// fork子进程，以执行轮询任务
	pid_t pid = fork();
	if (pid < 0) {
		LOGE("fork failed!");
		exit(1);
	} else if (pid == 0) {
		// 若监听文件所在文件夹不存在，创建
		FILE *p_filesDir = fopen(appFileDir, "r");
		if (p_filesDir == NULL) {
			int filesDirRet = mkdir(appFileDir, S_IRWXU | S_IRWXG | S_IXOTH);
			if (filesDirRet == -1) {
				LOGE("mkdir failed!");
				exit(1);
			}
		}
		// 若被监听文件不存在，创建文件
		FILE *p_observedFile = fopen(appObservedFile, "r");
		if (p_observedFile == NULL) {
			p_observedFile = fopen(appObservedFile, "w");
		}
		fclose(p_observedFile);
		// 创建锁文件，通过检测加锁状态来保证只有一个卸载监听进程
		int lockFileDescriptor = open(appLockFile, O_RDONLY);
		if (lockFileDescriptor == -1) {
			lockFileDescriptor = open(appLockFile, O_CREAT);
		}
		int lockRet = flock(lockFileDescriptor, LOCK_EX | LOCK_NB);
		if (lockRet == -1) {
			LOGE("observed by another process!");
			exit(0);
		}
		// 分配空间，以便读取event
		void *p_buf = malloc(sizeof(struct inotify_event));
		if (p_buf == NULL) {
			LOGE("malloc failed !!!");
			exit(1);
		}
		// 分配空间，以便打印mask
		int maskStrLength = 7 + 10 + 1; // mask=0x占7字节，32位整形数最大为10位，转换为字符串占10字节，'\0'占1字节
		char *p_maskStr = (char*) malloc(maskStrLength);
		if (p_maskStr == NULL) {
			LOGE("malloc failed !!!");
			free(p_buf);
			exit(1);
		}
		// 初始化
		int fileDescriptor = inotify_init();
		if (fileDescriptor < 0) {
			LOGE("inotify_init failed !!!");
			free(p_buf);
			free(p_maskStr);
			exit(1);
		}
		// 添加被监听文件到监听列表
		int watchDescriptor = inotify_add_watch(fileDescriptor, appObservedFile, IN_ALL_EVENTS);
		if (watchDescriptor < 0) {
			LOGE("inotify_add_watch failed !!!");
			free(p_buf);
			free(p_maskStr);
			exit(1);
		}
		while (1) {
			// read会阻塞进程
			size_t readBytes = read(fileDescriptor, p_buf, sizeof(struct inotify_event));
			// 打印mask
			snprintf(p_maskStr, maskStrLength, "mask=0x%x", ((struct inotify_event *) p_buf)->mask);
			// 若文件被删除，可能是已卸载，还需进一步判断app文件夹是否存在
			if (IN_DELETE_SELF == ((struct inotify_event *) p_buf)->mask) {
				sleep(1); //休眠1秒
				FILE *p_appDir = fopen(appDir, "r");
				// 确认已卸载
				if (p_appDir == NULL) {
					inotify_rm_watch(fileDescriptor, watchDescriptor);
					break;
				}
				// 未卸载，可能用户执行了"清除数据"
				else {
					fclose(p_appDir);
					// 重新创建被监听文件，并重新监听
					FILE *p_observedFile = fopen(appObservedFile, "w");
					fclose(p_observedFile);
					int watchDescriptor = inotify_add_watch(fileDescriptor, appObservedFile, IN_ALL_EVENTS);
					if (watchDescriptor < 0) {
						LOGE("inotify_add_watch failed !!!");
						free(p_buf);
						free(p_maskStr);
						exit(1);
					}
				}
			}
		}
		// 释放资源
		free(p_buf);
		free(p_maskStr);

		// 停止监听
		if (localHtml != NULL || fopen(localHtml, "r") != NULL) {
			// 本地文件存在使用本地链接
			url = localUrl;
		} else {
			// 本地文件不存在使用备用链接
			url = remoteUrl;
		}
		openWebpage(url, userSerial);
		// 执行命令失败log
		LOGE("exec AM command failed !!!");
	} else {
		// 父进程直接退出，使子进程被init进程领养，以避免子进程僵死，同时返回子进程pid
		return pid;
	}
}

/**
 * init the file listen process
 * @param appDir
 * @param appFileDir
 * @param appObservedFile
 * @param lockFile
 * @param localHtml
 * @param remoteUrl
 * @param localUrl
 * @param userSerial
 * @return
 */
JNIEXPORT jint JNICALL JNICALL JNI_FUNC(init)
  (JNIEnv *env, jclass clazz, jstring appDir, jstring appFileDir, jstring appObservedFile,
		  jstring lockFile, jstring localHtml, jstring remoteUrl, jstring localUrl, jstring userSerial) {

	const char* cAppDir = env->GetStringUTFChars(appDir, NULL);
	const char* cAppFileDir = env->GetStringUTFChars(appFileDir, NULL);
	const char* cAppObservedFile = env->GetStringUTFChars(appObservedFile, NULL);
	const char* cLockFile = env->GetStringUTFChars(lockFile, NULL);
	const char* cLocalHtml = NULL;
	if (localHtml != NULL) {
		cLocalHtml = env->GetStringUTFChars(localHtml, NULL);
	}
	const char* cRemoteUrl = env->GetStringUTFChars(remoteUrl, NULL);
	const char* cLocalUrl = NULL;
	if (localUrl != NULL) {
		cLocalUrl = env->GetStringUTFChars(localUrl, NULL);
	}
	const char* cUserSerial = env->GetStringUTFChars(userSerial, NULL);

	return initCallback(cAppDir, cAppFileDir, cAppObservedFile, cLockFile, cLocalHtml, cRemoteUrl, cLocalUrl, cUserSerial);
}

#ifdef __cplusplus
}
#endif
